package com.cooqe.collection_snmp.utils;

import com.cooqe.collection_snmp.model.SnmpParameter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.snmp4j.*;
import org.snmp4j.mp.MPv1;
import org.snmp4j.mp.MPv2c;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.*;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultUdpTransportMapping;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Vector;

/**
 * @author 折戟沉沙铁未销
 * @version V1.0
 * @date 2023/11/14-2023
 * @Description: snmp util 工具类
 */
@Slf4j
public class SnmpUtil {

    //定义常量
    public static final String AES256 = "aes256";
    public static final String AES192 = "aes192";
    public static final String AES128 = "aes128";
    public static final String _3DES = "3des";
    public static final String DES = "des";
    public static final String SHA = "sha";
    public static final String MD5 = "md5";
    public static final String SNMP_INDEX = "snmp_index";
    public static final String REGEX_TEXT = ":";
    public static final String REGEX_SEGMENT = ",";
    public static final String OIDS = "oids";
    public static final String VALUES = "values";
    public static final String DATATYPES = "dataTypes";


    //无效的OID
    public static final int OID_INVALID = 1;
    //成功
    public static final int SUCCESS = 0;
    //超时
    public static final int TIMEOUT = 2;

    //初始化 snmp 对象
    public static Snmp createSnmp() {
        Snmp snmp = null;
        try {
            DefaultUdpTransportMapping transportMapping = new DefaultUdpTransportMapping();
            transportMapping.setReceiveBufferSize(50 * 1024 * 1024);

            USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(
                    MPv3.createLocalEngineID(new OctetString("MyUniqueID" + System.currentTimeMillis()))),
                    0);

            snmp = new Snmp();
            snmp.addTransportMapping(transportMapping);
            SecurityProtocols.getInstance().addDefaultProtocols();
            snmp.getMessageDispatcher().addMessageProcessingModel(new MPv1());
            snmp.getMessageDispatcher().addMessageProcessingModel(new MPv2c());
            snmp.getMessageDispatcher().addMessageProcessingModel(new MPv3(usm));
            snmp.getMessageDispatcher().addCommandResponder(snmp);
            snmp.listen();
        } catch (IOException e) {
            log.warn(e.getMessage());
        }
        return snmp;
    }

    //创建 target 对象
    @SuppressWarnings("all")
    public static Target createTarget(SnmpParameter p, Snmp snmp) {
        // v3 版本的
        if (p.getVersion() == SnmpConstants.version3) {
            UserTarget target = new UserTarget();
            target.setSecurityLevel(p.getSecurityLevel());
            OctetString securityName = new OctetString(p.getSecurityName());
            OctetString authPassphrase = null;
            OctetString privacyPassphrase = null;
            OID authProtocol = null;
            OID privacyProtocol = null;
            // 认证加密
            if (StringUtils.isNotBlank(p.getAuthPassphrase()) && p.getSecurityLevel() != SecurityLevel.NOAUTH_NOPRIV) {
                authPassphrase = new OctetString(p.getAuthPassphrase());
                if (StringUtils.equalsIgnoreCase(p.getAuthProtocol(), MD5)) {
                    authProtocol = AuthMD5.ID;
                } else if (StringUtils.equalsIgnoreCase(p.getAuthProtocol(), SHA)) {
                    authProtocol = AuthSHA.ID;
                }
            }
            // 加密算法
            if (StringUtils.isNotBlank(p.getPrivacyPassphrase()) && p.getSecurityLevel() == SecurityLevel.AUTH_PRIV) {
                privacyPassphrase = new OctetString(p.getPrivacyPassphrase());
                if (StringUtils.equalsIgnoreCase(p.getPrivacyProtocol(), DES)) {
                    privacyProtocol = PrivDES.ID;
                } else if (StringUtils.equalsIgnoreCase(p.getPrivacyProtocol(), _3DES)) {
                    privacyProtocol = Priv3DES.ID;
                } else if (StringUtils.equalsIgnoreCase(p.getPrivacyProtocol(), AES128)) {
                    privacyProtocol = PrivAES128.ID;
                } else if (StringUtils.equalsIgnoreCase(p.getPrivacyProtocol(), AES192)) {
                    privacyProtocol = PrivAES192.ID;
                } else if (StringUtils.equalsIgnoreCase(p.getPrivacyProtocol(), AES256)) {
                    privacyProtocol = PrivAES256.ID;
                }
            }
            target.setSecurityName(securityName);
            UsmUser user = new UsmUser(securityName, authProtocol, authPassphrase, privacyProtocol, privacyPassphrase);
            snmp.getUSM().addUser(securityName, user);
            return target;
        }

        // snmp v1 or v2c
        CommunityTarget target = new CommunityTarget();
        target.setCommunity(new OctetString(p.getCommunity()));

        // snmp agent 地址
        Address address = new UdpAddress(p.getIp() + "/" + p.getPort());
        target.setAddress(address);
        // 通信不成功时的重试次数
        target.setRetries(p.getSnmpRetry());
        // 超时时间
        target.setTimeout(p.getSnmpTimeout());
        // 版本
        target.setVersion(p.getVersion());

        //返回
        return target;
    }

    /**
     * 判断SNMP walk是否已经结束，只 walk一层节点
     *
     */
    public static boolean isEndWalk(VariableBinding responseVariableBinding, OID requestOID, PDU responsePDU) {
        boolean isFinished = false;
        //有错误，需要 end
        if (responsePDU.getErrorStatus() != PDU.noError) {
            isFinished = true;
        }
        //响应中得不到oid，需要 end
        else if (responseVariableBinding.getOid() == null) {
            isFinished = true;
        }
        //响应oid size 小于请求的 oid(不应该存在吧？)，需要 end
        else if (responseVariableBinding.getOid().size() < requestOID.size()) {
            isFinished = true;
        }
        // (1.3.6.1.2.1.2.2.1.6,  1.3.6.1.2.1.2.2.1.6) 完全一样  ===0
        // (1.3.6.1.2.1.2.2.1.6.58,  1.3.6.1.2.1.2.2.1.6.58) 完全一样  ===0
        // (1.3.6.1.2.1.2.2.1.6,  1.3.6.1.2.1.2.2.1.6.58) 后面是子节点 ===0
        // (1.3.6.1.2.1.2.2.1.6.58,  1.3.6.1.2.1.2.2.1.6) 后面是子节点 ===1
        else if (requestOID.leftMostCompare(requestOID.size(), responseVariableBinding.getOid()) != 0) {
            isFinished = true;
        }
        //异常。 需要end
        else if (Null.isExceptionSyntax(responseVariableBinding.getVariable().getSyntax())) {
            isFinished = true;
        } else if (responseVariableBinding.getOid().compareTo(requestOID) <= 0) {
            log.warn("本次  snmpWalk 请求 获取到的结果不是请求OID 下的叶子节点!!!");
            isFinished = true;
        }
        return isFinished;
    }

    /**
     * 解析返回结果
     *
     */
    public static void resolveSnmpReturnValue(Map<String, List<String>> oidResultValueMaps,
                                              int pduCode, Vector<? extends VariableBinding> bindings,
                                              String requestOid, String initOid) {
        //判断是否为 getNext 的请求
        boolean parseIndex = (pduCode == PDU.GETNEXT);
        //当为 getNext 请求的时候，判断 oidResultValueMaps 中是否含有 snmp_index 该key 对应的 ArrayList(), 该 ArrayList维护了一个 索引集合。
        if (parseIndex) {
            if (!oidResultValueMaps.containsKey("snmp_index")) {
                oidResultValueMaps.put("snmp_index", new ArrayList<>());
            }
        }

        //遍历 bindings 返回结果。
        String lastSourceOid = null;
        for (int i = 0; i < bindings.size(); i++) {
            VariableBinding variableBinding = bindings.get(i);
            String snmpReturnoid = variableBinding.getOid().toString();
            lastSourceOid = getSourceOid(lastSourceOid, requestOid, snmpReturnoid);

            try {
                if (lastSourceOid == null) {
                    continue;
                }
                int lastIndex = lastSourceOid.length();
                // getNext  的请求方式
                if (parseIndex) {
                    if (i == 0) {
                        String index = snmpReturnoid.substring(lastIndex + 1);
                        if (!oidResultValueMaps.get("snmp_index").contains(index)) {
                            oidResultValueMaps.get("snmp_index").add(index);
                        }
                    }
                }
                if (oidResultValueMaps.containsKey(initOid)) {
                    oidResultValueMaps.get(initOid).add(String.valueOf(variableBinding.getVariable()));
                } else {
                    List<String> varValues = new ArrayList<>();
                    varValues.add(String.valueOf(variableBinding.getVariable()));
                    oidResultValueMaps.put(initOid, varValues);
                }
            } catch (Exception e) {
                log.warn(e.getMessage(), e);
            }
        }
    }

    /**
     * 获取 sourceId
     *
     */
    public static String getSourceOid(String lastSourceOid, String requestOid, String snmpResponseOid) {
        if (lastSourceOid != null && snmpResponseOid.startsWith(lastSourceOid)) {
            return lastSourceOid;
        } else {
            if (snmpResponseOid.startsWith(requestOid)) {
                return requestOid;
            }
        }
        return null;
    }
}